/**
 * @fileoverview Logic for Inspectres! Live!
 */

/**
 * Whether to display debug information in the app.
 * @type {boolean}
 * @const
 */
var DEBUG = true;

var RESOURCE_PATH = 'http://inspectres-live.appspot.com/';

/**
 * Namespace for the entire app.
 */
inspectres = {};


/** Controls the full inspectres app experience. */
inspectres.Controller = function() {

  /**
   * HTML body.
   * @type {Element}
   */
  var body_ = null;

  /**
   * HTML body's canvas.
   * @type {Element}
   */
  var canvas_ = null;

  /**
   * HTML body's sidebar.
   * @type {Element}
   */
  var sidebar_ = null;

  /**
   * HTML body's content.
   * @type {Element}
   */
  var content_ = null;

  /**
   * Controls access to the confessional.
   * @type {inspectres.ConfessionalController}
   * @private
   */
  var confessionalController_ = null;

  /**
   * Controls access to the Franchise.
   * @type {inspectres.FranchiseController}
   * @private
   */
  var franchiseController_ = null;

  /**
   * Controls access to the Character.
   * @type {inspectres.CharacterController}
   * @private
   */
  var characterController_ = null;

  /**
   * Stores information about the active participant.
   */
  var participant_ = null;

  inspectres.Controller.prototype.init = function(participant) {
    console.log('Controller.init: %o', participant);
    this.participant_ = participant;

    this.confessionalController_ = new inspectres.ConfessionalController();
    this.franchiseController_ = new inspectres.FranchiseController();
    this.characterController_ = new inspectres.CharacterController();
  };

  inspectres.Controller.prototype.setConfessional = function(confessional) {
    this.confessionalController_.setConfessional(confessional);
  };

  inspectres.Controller.prototype.setFranchise = function(franchise) {
    this.franchiseController_.setFranchise(franchise);
  };

  inspectres.Controller.prototype.setCharacter = function(character) {
    this.characterController_.setCharacter(character);
  };

  inspectres.Controller.prototype.attach = function(body) {
    this.canvas_ = $('#canvas');
    this.sidebar_ = $('#sidebar');
    this.content_ = $('#content');

    gapi.hangout.layout.getVideoCanvas().setWidth(800);
    gapi.hangout.layout.getVideoCanvas().setPosition({top: 0, left: 500});
    gapi.hangout.layout.getVideoCanvas().setVisible(true);

    this.confessionalController_.attach(body);
    this.franchiseController_.attach(body);
    this.characterController_.attach(body);
  };

  inspectres.Controller.prototype.update = function() {
    this.confessionalController_.update();
    this.franchiseController_.update();
    this.characterController_.update();
  }

  inspectres.Controller.prototype.set = function(participant) {
    // assert (participant == gapi.hangout.getLocalId());
    this.participant_ = participant;
  };

  inspectres.Controller.prototype.get = function() {
    return this.participant_;
  };

  inspectres.Controller.prototype.createHangoutEventHandler = function() {
    var that = this;
    return function(e) {
      that.handleHangoutEvent(e);
    };
  };

  /**
   * Called when a hangout event has called that will impact a participant's media UI.
   *
   * @param {Event} the event that the hangout triggered.
   */
  inspectres.Controller.prototype.handleHangoutEvent = function(e) {
    console.log('Controller.handleHangoutEvent: %o', e);
    for (var i = 0; i < e.addedKeys.length; i++) {
      var key = e.addedKeys[i].key;
      if (key == inspectres.ConfessionalController.STATE_KEY) {
        this.confessionalController_.sync();
      } else if (key == inspectres.FranchiseController.STATE_KEY) {
        this.franchiseController_.sync();
      };
    }
    for (var i = 0; i < e.removedKeys.length; i++) {
      var key = e.removedKeys[i];
      if (key == inspectres.ConfessionalController.STATE_KEY) {
        this.confessionalController_.sync();
      } else if (key == inspectres.FranchiseController.STATE_KEY) {
        this.franchiseController_.sync();
      };
    }
  };

  inspectres.Controller.prototype.isConfessor_ = function(confessor) {
    return confessor && confessor == gapi.hangout.getLocalParticipantId();
  };
};


/**
 * Controls who is in the confession chair.
 */
inspectres.ConfessionalController = function() {

  /**
   * Used in shared state to track the confessor.
   */
  inspectres.ConfessionalController.STATE_KEY = 'confessional';

  /**
   * Allows someone to take the confession seat.
   */
  var confessButton_ = null;

  /**
   * The image used as an overlay.
   */
  var overlayResource_ = null;

  /**
   * The overlay to be shown on the confesor's video feed.
   */
  var overlay_ = null;

  /**
   * The state of the confessional.
   */
  var confessional_ = null;

  inspectres.ConfessionalController.prototype.setConfessional = function(confessional) {
    this.confessional_ = confessional;
    return this;
  };

  inspectres.ConfessionalController.prototype.attach = function(body) {
    console.log('ConfessionalController.attach');
    this.confessButton_ = $('<input />')
        .attr('id', 'confessButton')
        .attr('type', 'button')
        .click(this.createOnClickHandler_());
    body.append(this.confessButton_);

    // Load required resources.
    // TODO(jlapenna): The onLoad event seems to be getting called mulitple
    // times, every time that 
    this.overlayResource_ = gapi.hangout.av.effects.createImageResource(
        RESOURCE_PATH + 'static/img/confession_overlay.png');

    var that = this;
    that.overlayResource_.onLoad.add(function(e) {
      console.log('Overlay resource onLoad: %o', e);
      if (!e.isLoaded) {
        console.log('FAILED: load confessional_overlay.png: %o', e);
        that.overlayResource_.dispose();
        return;
      } else if (that.overlay_ == null) {
        that.overlay_ = that.overlayResource_.createOverlay(
            {'position': {'x': 0, 'y': .38}})
        that.overlay_.setScale(1, gapi.hangout.av.effects.ScaleReference.WIDTH);
        console.log('Created overlay: %o', that.overlay_);
      } else {
        // Not sure why, but in some cases, onLoad is being triggered twice.
        console.log('Overlay already created: %o', that.overlay_);
      }
    });
  };

  inspectres.ConfessionalController.prototype.createOnClickHandler_ = function() {
    // TODO(jlapenna): there should be some jQuery.partial( like call here.
    var that = this;
    return function(e) {
      that.onClickButton_(e);
    }
  };

  inspectres.ConfessionalController.prototype.onClickButton_ = function(e) {
    console.log('ConfessionalController.onClickButton_: %o / %o', this, e);
    if (this.confessional_ == null) {
      // Still loading, try again later!
      this.confessButton_.val('Still Loading..');
    } else if (this.confessional_.getConfessor() == null) {
      // The user is trying to enter.
      console.log('Attempting to enter: %o', gapi.hangout.getLocalParticipantId());
      this.confessional_.setConfessor(
           gapi.hangout.getLocalParticipantId());
      gapi.hangout.data.setValue(inspectres.ConfessionalController.STATE_KEY,
          this.confessional_.serialize());

    } else if (this.confessional_.getConfessor() == gapi.hangout.getLocalParticipantId()) {
      // The user is trying to leave.
      console.log('Attempting to leave');
      this.confessional_.setConfessor(null);
      gapi.hangout.data.setValue(inspectres.ConfessionalController.STATE_KEY,
          this.confessional_.serialize());

    } else {
      console.log('non-confessor tried to click the confess button');
    };
  };

  inspectres.ConfessionalController.prototype.update = function() {
    console.log('ConfessionalController.update: %o', this.confessional_);
    if (this.confessional_ == null) {
      this.confessButton_.val('Loading');

    } else if (this.confessional_.getConfessor() == null) {
      this.confessButton_.val('Confess!');
      this.clearConfessional();

    } else if (this.confessional_.getConfessor() == gapi.hangout.getLocalParticipantId()) {
      this.confessButton_.val('End confession');
      this.enterConfessional();

    } else {
      // Someone else is the confessor.
      this.confessButton_.val('Watching confession');
      this.observeConfessional();
    }
  };

  /**
   * Reads confessional details from shared state and updates the UI.
   */
  inspectres.ConfessionalController.prototype.sync = function() {
    this.confessional_ = models.Confessional.fromJSON(
        gapi.hangout.data.getValue(inspectres.ConfessionalController.STATE_KEY));
    this.update();
  }

  /**
   * Called on any event where a confessional has started and the active
   * participant is an observer.
   */
  inspectres.ConfessionalController.prototype.observeConfessional = function() {
    console.log('ConfessionalController.observeConfessional');
    gapi.hangout.av.setMicrophoneMute(true);
    gapi.hangout.av.setCameraMute(true);
  };

  /**
   * Called when the active participant enters a confessional.
   */
  inspectres.ConfessionalController.prototype.enterConfessional = function() {
    console.log('ConfessionalController.enterConfessional: %o',
        this.confessional_.getConfessor());
    console.log('Setting video focus to %s for %s', this.confessional_.getConfessor(), 
        gapi.hangout.getLocalParticipantId());
    var videoFeed = gapi.hangout.layout.createParticipantVideoFeed(
        this.confessional_.getConfessor());
    gapi.hangout.layout.getVideoCanvas().setVideoFeed(videoFeed);
    console.log('Setting video overlay for confessional: %o', this.overlay_);
    this.overlay_.setVisible(true);
    console.log('Overlay visible');
  };

  /**
   * Called when any participant leaves a confessional.
   */
  inspectres.ConfessionalController.prototype.clearConfessional = function() {
    console.log('ConfessionalController.clearConfessional');
    // overlay may be null because the resource hasn't been loaded yet.
    if (this.overlay_ != null) {
      console.log('Hiding overlay: %o', this.overlay_);
      this.overlay_.setVisible(false);
    };

    // Ensures that all participants can speak and be seen.
    gapi.hangout.av.clearMicrophoneMute();
    gapi.hangout.av.clearCameraMute();

    // Ensures that the camera is back to focusing on the most recent speaker.
    gapi.hangout.layout.getVideoCanvas().setVideoFeed(
        gapi.hangout.layout.getDefaultVideoFeed());
  };
};


/**
 * Controls the franchise.
 */
inspectres.FranchiseController = function() {

  /**
   * Element describing the state of the franchise.
   * @type {Element}
   */
  var franchiseDiv_ = null;

  /**
   * Element describing the franchise's name.
   * @type {Element}
   */
  var franchiseNameDiv_ = null;

  /**
   * Element describing the franchise's gym score.
   * @type {Element}
   */
  var franchiseGymDiv_ = null;

  /**
   * Element describing the franchise's credit score.
   * @type {Element}
   */
  var franchiseCreditDiv_ = null;

  /**
   * Element describing the franchise's library score.
   * @type {Element}
   */
  var franchiseLibraryDiv_ = null;

  /**
   * Element describing the franchise's bank account.
   * @type {Element}
   */
  var franchiseBankDiv_ = null;

  /**
   * The franchise being controlled.
   * @type {inspectres.Franchise}
   */
  var franchise_ = null;

  /**
   * Key used to pull state from hangout shared state.
   */
  inspectres.FranchiseController.STATE_KEY = 'franchise';

  inspectres.FranchiseController.prototype.setFranchise = function(franchise) {
    this.franchise_ = franchise;
  };

  inspectres.FranchiseController.prototype.attach = function(body) {
    this.franchiseDiv_= $('#franchise');
    this.franchiseNameDiv_ = $('#franchise-name');
    this.franchiseGymDiv_ = $('#franchise-stats-gym-value');
    this.franchiseCreditDiv_ = $('#franchise-stats-credit-value');
    this.franchiseLibraryDiv_ = $('#franchise-stats-library-value');
    this.franchiseBankDiv_ = $('#franchise-stats-bank-value');
  };

  inspectres.FranchiseController.prototype.update = function() {
    this.franchiseNameDiv_.text(this.franchise_.getName());
    this.franchiseGymDiv_.text(this.franchise_.getGym());
    this.franchiseCreditDiv_.text(this.franchise_.getCredit());
    this.franchiseLibraryDiv_.text(this.franchise_.getLibrary());
    this.franchiseBankDiv_.text(this.franchise_.getBank());
  };

  /**
   * Reads franchise stats from shared state and updates the UI.
   */
  inspectres.FranchiseController.prototype.sync = function() {
    this.franchise_ = models.Franchise.fromJSON(
        gapi.hangout.data.getValue(inspectres.FranchiseController.STATE_KEY));
    this.update();
  }

  /**
   * Pushes a change to the franchise's name to shared state.
   * @param {String} the new name of the franchise.
   */
  inspectres.FranchiseController.prototype.setName = function(name) {
    this.franchise_.setName(name);

    // Sends the latest franchise state to shared state.
    gapi.hangout.data.setValue(inspectres.FranchiseController.STATE_KEY,
        this.franchise_.serialize());
  };
};

/**
 * Controls the active participant's character.
 */
inspectres.CharacterController = function() {

  /**
   * Element describing the character's name.
   * @type {Element}
   */
  var characterNameDiv_ = null;

  /**
   * Element describing the character's name talent.
   * @type {Element}
   */
  var characterTalentDiv_ = null;

  /**
   * Element describing the character's academics score.
   * @type {Element}
   */
  var characterAcademicsDiv_ = null;

  /**
   * Element describing the character's athletics score.
   * @type {Element}
   */
  var characterAthleticsDiv_ = null;

  /**
   * Element describing the character's technology score.
   * @type {Element}
   */
  var characterTechnologyDiv_ = null;

  /**
   * Element describing the character's contact score.
   * @type {Element}
   */
  var characterContactDiv_ = null;

  /**
   * Element describing the character's cool score.
   * @type {Element}
   */
  var characterCoolDiv_ = null;

  /**
   * Element describing the character's cool score.
   * @type {Element}
   */
  var characterBiographyDiv_ = null;

  /**
   * The character being controlled.
   * @type {inspectres.Character}
   */
  var character_ = null;

  inspectres.CharacterController.prototype.setCharacter = function(character) {
    this.character_ = character;
  };

  inspectres.CharacterController.prototype.attach = function(body) {
    this.characterDiv_ = $('#character');
    this.characterNameDiv_ = $('#character-name');
    this.characterTalentDiv_ = $('#character-talent');
    this.characterAcademicsDiv_ = $('#character-stat-academics-value');
    this.characterAthleticsDiv_ = $('#character-stat-athletics-value');
    this.characterTechnologyDiv_ = $('#character-stat-technology-value');
    this.characterContactDiv_ = $('#character-stat-contact-value');
    this.characterCoolDiv_ = $('#character-stat-cool-value');
    this.characterBiographyDiv_ = $('#character-biography');
  };

  inspectres.CharacterController.prototype.update = function() {
    this.characterNameDiv_.text(this.character_.getName());
    this.characterTalentDiv_.text(this.character_.getTalent());
    this.characterAcademicsDiv_.text(this.character_.getAcademics());
    this.characterAthleticsDiv_.text(this.character_.getAthletics());
    this.characterTechnologyDiv_.text(this.character_.getTechnology());
    this.characterContactDiv_.text(this.character_.getContact());
    this.characterCoolDiv_.text(this.character_.getCool());
    this.characterBiographyDiv_.text(this.character_.getBiography());
  };
};


/** main/init */
inspectres.main = function() {
  console.log('Inspectres: Main')

  var controller = new inspectres.Controller();
  // If I knew how to do a constructor this call wouldn't exist.
  controller.init(new models.Participant().setId(gapi.hangout.getLocalParticipantId()));

  // Builds the UI.
  controller.attach($('body'));

  var confessional = new models.Confessional();
  controller.setConfessional(confessional);

  var franchise = new models.Franchise()
      .setName('Ghostbusters!')
      .setGym(5)
      .setCredit(6)
      .setLibrary(7)
      .setBank(8);
  controller.setFranchise(franchise);

  var character = new models.Character()
      .setName('Ray Stands')
      .setTalent('Bustin ghosts!')
      .setAcademics(9)
      .setAthletics(10)
      .setTechnology(11)
      .setContact(12)
      .setCool(13)
      .setBiography('I was born to bust ghosts.');
  controller.setCharacter(character);

  // This causes clearConfessional to execute. That shouldn't happen yet...
  controller.update();

  // Make sure the UI can do stuff.
  console.log('Adding state change handler');
  gapi.hangout.data.onStateChanged.add(controller.createHangoutEventHandler());
  console.log('Inspectres: Running');

};


gadgets.util.registerOnLoadHandler(function() {
  console.log('Gadgets: OnLoadHandler')
  var apiReady = function(eventObj) {
    console.log('API Ready');
    inspectres.main();
  };
  gapi.hangout.onApiReady.add(apiReady);
});
